package tolgahan.albayrak.as3.xml
{
	import tolgahan.albayrak.as3.reflection.Primitives;
	import tolgahan.albayrak.as3.reflection.PropertyInfo;
	import tolgahan.albayrak.as3.reflection.Type;
	import tolgahan.albayrak.as3.reflection.converters.TypeConverter;

	public class XMLObjectDecoder
	{
		public function XMLObjectDecoder()
		{
		}
		
		public function canCreateInstance(type:Type):Boolean
		{
			
		}
		
		public function getConstructorParams(type:Type):Array
		{
			return null;
		}
		
		public function instanceCreated(type:Type, instance:*):void
		{
			
		}
		
		public function xmlObjectInstanceCreated(type:Type, instance:*):void
		{
			
		}
		
		public function canLoadProperties(type:Type, instance:*):Boolean
		{
			return true;
		}
		
		public function canXMLObjectLoadProperties(type:Type, instance:*):Boolean
		{
			return true;
		}
		
		public function beforeSetProperty(instance:*, property:PropertyInfo, value:String, converter:TypeConverter):Boolean
		{
			return true;
		}
		public function beforeXMLObjectSetProperty(instance:*, property:PropertyInfo, value:String, converter:TypeConverter):Boolean
		{
			return true;
		}
		public function afterSetProperty(instance:*, property:PropertyInfo, value:*):void
		{
			
		}
		public function afterXMLObjectSetProperty(instance:*, property:PropertyInfo, value:*):void
		{
			
		}
		public function canHaveChildren(mainType:Type, instance:*):Boolean
		{
			return true;
		}
		public function canXMLObjectHaveChildren(mainType:Type, instance:*):Boolean
		{
			return true;
		}
		public function beforeCreateChildren(mainType:Type, instance:*, childType:Type):Boolean
		{
			return true;
		}
		public function beforeXMLObjectCreateChildren(mainType:Type, instance:*, childType:Type):Boolean
		{
			return true;
		}
		public function childCreated(mainType:Type, instance:*, childType:Type, chilInstance:*):void
		{
			
		}
		public function xmlObjectChildCreated(mainType:Type, instance:*, childType:Type, chilInstance:*):void
		{
			
		}
		
		public function loadFromRootInstance(xml:XML, instance:*):TypeInstance
		{
			var type:Type = Type.GetTypeByInstance(instance);
			if(xml && !type.isMissing)
			{
				var name:String = String(xml.name());
				var type:Type = Type.GetTypeByName(name);
				var prop:XML, child:XML, childType:Type, 
				ixml:IXMLObject, pi:PropertyInfo,
				propName:String, value:*,
					converter:TypeConverter, ti:TypeInstance;
				if(instance is IXMLObject)
				{
					ixml = IXMLObject(instance);
					ixml.xmlObjectInit(xml);
					if(ixml.canLoadProperties && canXMLObjectLoadProperties(type, instance))
					{
						for each(prop in xml.attributes())
						{
							propName = String(prop.name());
							pi = type.getProperty(propName);
							if(pi)
							{
								converter = Type.GetConverter(pi.type);
								value = String(prop);
								if(!converter || (converter.canConvertFrom(Primitives.StringType)))
								{
									if(ixml.beforeSetProperty(pi, value, converter) && beforeXMLObjectSetProperty(instance, pi, value, converter))
									{
										if(converter)
											value = converter.converFrom(value);
										pi.setValue(instance, value, false);
										ixml.afterSetProperty(pi, value);
										afterXMLObjectSetProperty(instance, pi, value);
									}
								}
							}
						}
					}
					if(ixml.canHaveChildren && canXMLObjectHaveChildren(type, instance))
					{
						for each(child in xml.children())
						{
							childType = Type.GetTypeByName(String(child.name()));
							if(ixml.beforeCreateChildren(childType) && beforeXMLObjectCreateChildren(type, instance, childType))
							{
								ti = load(child);
								if(ti)
								{
									ixml.afterCreateChildren(ti.type, ti.instance);
									xmlObjectChildCreated(type, instance, ti.type, ti.instance);
								}
							}
						}
					}
				}else{
					if(canLoadProperties(type, instance))
					{
						for each(prop in xml.attributes())
						{
							propName = String(prop.name());
							pi = type.getProperty(propName);
							if(pi)
							{
								converter = Type.GetConverter(pi.type);
								value = String(prop);
								if(!converter || (converter.canConvertFrom(Primitives.StringType)))
								{
									if(beforeSetProperty(instance, pi, value, converter))
									{
										if(converter)
											value = converter.converFrom(value);
										pi.setValue(instance, value, false);
										afterSetProperty(instance, pi, value);
									}
								}
							}
						}
					}
					if(canHaveChildren(type, instance))
					{
						for each(child in xml.children())
						{
							childType = Type.GetTypeByName(String(child.name()));
							if(beforeCreateChildren(type, instance, childType))
							{
								ti = load(child);
								if(ti)
								{
									childCreated(type, instance, ti.type, ti.instance);
								}
							}
						}								
					}
				}
			}
			return new TypeInstance(type, instance);
		}
		
		public function load(xml:XML):TypeInstance
		{
			if(!xml)
				return null;
			var name:String = String(xml.name());
			var type:Type = Type.GetTypeByName(name);
			var prop:XML, child:XML, childType:Type, 
				ixml:IXMLObject, pi:PropertyInfo,
				propName:String, value:*,
				converter:TypeConverter, ti:TypeInstance;
			if(!type.isMissing)
			{
				if(type.canCreateInstance && canCreateInstance(type))
				{
					var instance:* = type.constructor.ctor(getConstructorParams(type));
					if(instance)
					{
						xmlObjectInstanceCreated(type, instance);
						if(instance is IXMLObject)
						{
							ixml = IXMLObject(instance);
							ixml.xmlObjectInit(xml);
							if(ixml.canLoadProperties && canXMLObjectLoadProperties(type, instance))
							{
								for each(prop in xml.attributes())
								{
									propName = String(prop.name());
									pi = type.getProperty(propName);
									if(pi)
									{
										converter = Type.GetConverter(pi.type);
										value = String(prop);
										if(!converter || (converter.canConvertFrom(Primitives.StringType)))
										{
											if(ixml.beforeSetProperty(pi, value, converter) && beforeXMLObjectSetProperty(instance, pi, value, converter))
											{
												if(converter)
													value = converter.converFrom(value);
												pi.setValue(instance, value, false);
												ixml.afterSetProperty(pi, value);
												afterXMLObjectSetProperty(instance, pi, value);
											}
										}
									}
								}
							}
							if(ixml.canHaveChildren && canXMLObjectHaveChildren(type, instance))
							{
								for each(child in xml.children())
								{
									childType = Type.GetTypeByName(String(child.name()));
									if(ixml.beforeCreateChildren(childType) && beforeXMLObjectCreateChildren(type, instance, childType))
									{
										ti = load(child);
										if(ti)
										{
											ixml.afterCreateChildren(ti.type, ti.instance);
											xmlObjectChildCreated(type, instance, ti.type, ti.instance);
										}
									}
								}
							}
						}else{
							if(canLoadProperties(type, instance))
							{
								for each(prop in xml.attributes())
								{
									propName = String(prop.name());
									pi = type.getProperty(propName);
									if(pi)
									{
										converter = Type.GetConverter(pi.type);
										value = String(prop);
										if(!converter || (converter.canConvertFrom(Primitives.StringType)))
										{
											if(beforeSetProperty(instance, pi, value, converter))
											{
												if(converter)
													value = converter.converFrom(value);
												pi.setValue(instance, value, false);
												afterSetProperty(instance, pi, value);
											}
										}
									}
								}
							}
							if(canHaveChildren(type, instance))
							{
								for each(child in xml.children())
								{
									childType = Type.GetTypeByName(String(child.name()));
									if(beforeCreateChildren(type, instance, childType))
									{
										ti = load(child);
										if(ti)
										{
											childCreated(type, instance, ti.type, ti.instance);
										}
									}
								}								
							}
						}
						return new TypeInstance(type, instance);
					}
				}
			}
			return null;
		}
	}
}